namespace NBody
{
    using System;
    using System.IO;
    
    public class DataFormat
    {
        public enum Type { Basic, DncSingle, DncDouble, Gadget, Tipsy, Ascii };
        
        public static NBodySystem Read(string file, DataFormat.Type type)
        {
            switch (type)
            {
                case Type.Basic:
                    return NBodySystem.Read(file);
                    
                case Type.DncSingle:
                    return ReadDncSingle(file);
                    
                case Type.DncDouble:
                    return ReadDncDouble(file);
                    
                case Type.Gadget:
                    return ReadGadget(file);
                    
                case Type.Tipsy:
                    return null;
                    
                case Type.Ascii:
                    return ReadAscii(file);
            }
            
            return null;
        }
        
        public static void Write(NBodySystem s, string file, DataFormat.Type type)
        {
            switch (type)
            {
                case Type.Basic:
                    s.Write(file);
                    break;
                    
                case Type.DncSingle:
                    WriteDncSingle(s, file);
                    break;
                    
                case Type.DncDouble:
                    WriteDncDouble(s, file);
                    break;
                    
                case Type.Gadget:
                    WriteGadget(s, file);
                    break;
                    
                case Type.Tipsy:
                    WriteTipsy(s, file);
                    break;
                    
                case Type.Ascii:
                    WriteAscii(s, file);
                    break;
            }
        }
        
        private static NBodySystem ReadDncSingle(string file)
        {
            FileInfo info = new FileInfo(file);
            int n = (int)info.Length / 28; // assumes floats
            NBodySystem s = new NBodySystem(n, 0.0); // can't assume time
            
            Console.Error.WriteLine("Reading Dnc (single precision) format:");
            Console.Error.WriteLine("  Number of Particles = {0}", n);
            Console.Error.WriteLine("  Time = not available");
                    
            byte[] bytes;
            using (BinaryReader br = new BinaryReader(File.OpenRead(file)))
            {
                bytes = br.ReadBytes(n * 28);
            }
        
            for (int i = 0; i < n; i++)
            {
                float m = BitConverter.ToSingle(bytes, 28 * i);
                float x = BitConverter.ToSingle(bytes, 28 * i + 4);
                float y = BitConverter.ToSingle(bytes, 28 * i + 8);
                float z = BitConverter.ToSingle(bytes, 28 * i + 12);
                float vx = BitConverter.ToSingle(bytes, 28 * i + 16);
                float vy = BitConverter.ToSingle(bytes, 28 * i + 20);
                float vz = BitConverter.ToSingle(bytes, 28 * i + 24);

                s.AddParticle(new Particle(m, x, y, z, vx, vy, vz));
            }
            
            Console.Error.WriteLine("... done");
        
            return s;
        }
        
        private static NBodySystem ReadDncDouble(string file)
        {
            FileInfo info = new FileInfo(file);
            int n = (int)info.Length / 56; // assumes floats
            NBodySystem s = new NBodySystem(n, 0.0); // can't assume time
            
            Console.Error.WriteLine("Reading Dnc (double precision) format:");
            Console.Error.WriteLine("  Number of Particles = {0}", n);
            Console.Error.WriteLine("  Time = not available");
                    
            byte[] bytes;
            using (BinaryReader br = new BinaryReader(File.OpenRead(file)))
            {
                bytes = br.ReadBytes(n * 56);
            }
        
            for (int i = 0; i < n; i++)
            {
                double m = BitConverter.ToDouble(bytes, 56 * i);
                double x = BitConverter.ToDouble(bytes, 56 * i + 8);
                double y = BitConverter.ToDouble(bytes, 56 * i + 16);
                double z = BitConverter.ToDouble(bytes, 56 * i + 24);
                double vx = BitConverter.ToDouble(bytes, 56 * i + 32);
                double vy = BitConverter.ToDouble(bytes, 56 * i + 40);
                double vz = BitConverter.ToDouble(bytes, 56 * i + 48);

                s.AddParticle(new Particle((float)m, (float)x, (float)y, (float)z, (float)vx, (float)vy, (float)vz));
            }
            
            Console.Error.WriteLine("... done");
        
            return s;
        }
                        
        private static NBodySystem ReadGadget(string file)
        {
            FileInfo info = new FileInfo(file);
            byte[] bytes;
            using (BinaryReader br = new BinaryReader(File.OpenRead(file)))
            {
                bytes = br.ReadBytes((int)info.Length);
            }
            
            // number of particles
            int[] num_parts = new int[6];
            for (int i = 0; i < 6; i++)
                num_parts[i] = BitConverter.ToInt32(bytes, 4 + 4*i);
            
            Console.Error.WriteLine("Reading Gadget format:");
            for (int i = 0; i < 6; i++)
                Console.Error.WriteLine("  Number of Particles, type {0} = {1}", i, num_parts[i]);
            
            // mass of each kind of particle
            double[] mass = new double[6];
            for (int i = 0; i < 6; i++)
                mass[i] = BitConverter.ToDouble(bytes, 28 + 8*i);
            
            double time = BitConverter.ToDouble(bytes, 76);
            double redshift = BitConverter.ToDouble(bytes, 84);
            time = 276.5333776 * Math.Pow(1.0 + redshift, -1.5);
            Console.Error.WriteLine("  time = {0}", time);
            //int flag_sfr = BitConverter.ToInt32(bytes, 92);
            //int flag_feedback = BitConverter.ToInt32(bytes, 96);
            
            // number of particles (total)
            int[] num_parts_total = new int[6];
            for (int i = 0; i < 6; i++)
                num_parts_total[i] = BitConverter.ToInt32(bytes, 100 + 4*i);
            
            //int flag_cooling = BitConverter.ToInt32(bytes, 124);
            //int num_files = BitConverter.ToInt32(bytes, 128);
            //double boxsize = BitConverter.ToDouble(bytes, 132);
            //double omega_0 = BitConverter.ToDouble(bytes, 140);
            //double omega_lambda = BitConverter.ToDouble(bytes, 148);
            //double hubble_param = BitConverter.ToDouble(bytes, 156);
            
            // assume its one file for now ... and that num_part_tot = num_part
            
            int total_particles = 0;
            for (int i = 0; i < 6; i++)
                total_particles += num_parts[i];
            
            NBodySystem s = new NBodySystem(total_particles, time);
            int to_pos = 268;
            int to_vel = 268 + total_particles * 3 * 4 + 4 + 4;
            
            for (int i = 0; i < 6; i++)
            {
                for (int j = 0; j < num_parts[i]; j++)
                {
                    // assume no variable mass particles
                    float m = (float)mass[i];
                    float x = BitConverter.ToSingle(bytes, to_pos + 3*4*j);
                    float y = BitConverter.ToSingle(bytes, to_pos + 3*4*j + 4);
                    float z = BitConverter.ToSingle(bytes, to_pos + 3*4*j + 8);
                    float vx = BitConverter.ToSingle(bytes, to_vel + 3*4*j);
                    float vy = BitConverter.ToSingle(bytes, to_vel + 3*4*j + 4);
                    float vz = BitConverter.ToSingle(bytes, to_vel + 3*4*j + 8);
                    s.AddParticle(new Particle(m, x, y, z, vx, vy, vz));
                }
            }
            
            return s;
        }
        
        private static NBodySystem ReadAscii(string file)
        {
            StreamReader sr = new StreamReader(file);
            int n = Int32.Parse(sr.ReadLine());
            double time = Double.Parse(sr.ReadLine());
            
            Console.Error.WriteLine("n = {0}, time = {1}", n, time);
            
            NBodySystem s = new NBodySystem(n, time);
            for (int i = 0; i < n; i++)
            {
                string line = sr.ReadLine();
                string[] numbers = line.Split(null);
                double m = Double.Parse(numbers[0]);
                double x = Double.Parse(numbers[1]);
                double y = Double.Parse(numbers[2]);
                double z = Double.Parse(numbers[3]);
                double vx = Double.Parse(numbers[4]);
                double vy = Double.Parse(numbers[5]);
                double vz = Double.Parse(numbers[6]);
                                
                s.AddParticle(new Particle(m, x, y, z, vx, vy, vz));
            }
            
            return s;
        }   
            
        private static void WriteDncSingle(NBodySystem s, string file)
        {
            Console.Error.WriteLine("Writing Dnc (single precision) format:");
            Console.Error.WriteLine("  Number of Particles = {0}", s.NumParts);
            Console.Error.WriteLine("  Time = 0.0");
            
            byte[] buffer = new byte[28 * s.NumParts];
            for (int i = 0; i < s.NumParts; i++)
            {
                byte[] m = BitConverter.GetBytes(s[i].Mass);
                byte[] x = BitConverter.GetBytes(s[i].X);
                byte[] y = BitConverter.GetBytes(s[i].Y);
                byte[] z = BitConverter.GetBytes(s[i].Z);
                byte[] vx = BitConverter.GetBytes(s[i].Vx);
                byte[] vy = BitConverter.GetBytes(s[i].Vy);
                byte[] vz = BitConverter.GetBytes(s[i].Vz);
            
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j] = m[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 4] = x[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 8] = y[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 12] = z[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 16] = vx[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 20] = vy[j];
                for (int j = 0; j < 4; j++)
                    buffer[28 * i + j + 24] = vz[j];
            }            
        
            using (BinaryWriter bw = new BinaryWriter(File.Open(file, FileMode.Create)))
            {
                bw.Write(buffer);                
            }
            
            Console.Error.WriteLine("... done");
        }
        
        private static void WriteDncDouble(NBodySystem s, string file)
        {
            Console.Error.WriteLine("Writing Dnc (double precision) format:");
            Console.Error.WriteLine("  Number of Particles = {0}", s.NumParts);
            Console.Error.WriteLine("  Time = 0.0");
            
            byte[] buffer = new byte[56 * s.NumParts];
            for (int i = 0; i < s.NumParts; i++)
            {
                byte[] m = BitConverter.GetBytes((double)s[i].Mass);
                byte[] x = BitConverter.GetBytes((double)s[i].X);
                byte[] y = BitConverter.GetBytes((double)s[i].Y);
                byte[] z = BitConverter.GetBytes((double)s[i].Z);
                byte[] vx = BitConverter.GetBytes((double)s[i].Vx);
                byte[] vy = BitConverter.GetBytes((double)s[i].Vy);
                byte[] vz = BitConverter.GetBytes((double)s[i].Vz);
            
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j] = m[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 8] = x[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 16] = y[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 24] = z[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 32] = vx[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 40] = vy[j];
                for (int j = 0; j < 8; j++)
                    buffer[56 * i + j + 48] = vz[j];
            }            
        
            using (BinaryWriter bw = new BinaryWriter(File.Open(file, FileMode.Create)))
            {
                bw.Write(buffer);                
            }
            
            Console.Error.WriteLine("... done");
        }
        
        private static void WriteTipsy(NBodySystem s, string file)
        {
            Console.Error.WriteLine("Writing Tipsy format:");
            Console.Error.WriteLine("  Number of Particles = {0}", s.NumParts);
            Console.Error.WriteLine("  Time = 0.0");
            
            // tipsy header stuff
            int Ndim = 3;
            int Nsph = 0;
            int Nstar = 0; // assume all dark matter particles
            float eps = 0.0f;
            float phi = 0.0f;
            
            using (BinaryWriter bw = new BinaryWriter(File.Open(file, FileMode.Create)))
            {
                bw.Write(s.Time);
                bw.Write(s.NumParts);
                bw.Write(Ndim);
                bw.Write(Nsph);
                bw.Write(s.NumParts);
                bw.Write(Nstar);
                
                foreach (Particle p in s)
                {
                    bw.Write((float)p.Mass);
                    bw.Write((float)p.X);
                    bw.Write((float)p.Y);
                    bw.Write((float)p.Z);
                    bw.Write((float)p.Vx);
                    bw.Write((float)p.Vy);
                    bw.Write((float)p.Vz);
                    bw.Write(eps);
                    bw.Write(phi);
                }
            }
            
            Console.Error.WriteLine("... done");            
        }
        
        private static void WriteAscii(NBodySystem s, string file)
        { 
            StreamWriter sw = new StreamWriter(file);
            sw.WriteLine(s.NumParts);
            sw.WriteLine(s.Time);
            
            foreach (Particle p in s)
            {
                sw.WriteLine("{0:f10} {1:f10} {2:f10} {3:f10} {4:f10} {5:f10} {6:f10}", p.Mass, p.X, p.Y, p.Z, p.Vx, p.Vy, p.Vz);
            }
            sw.Close();
        }
        
        private static void WriteGadget(NBodySystem s, string file)
        {
            using (BinaryWriter bw = new BinaryWriter(File.Open(file, FileMode.Create)))
            {
                // header
                
                int[] npart = new int[] {0, 0, 0, 0, 0, 0 };
                npart[1] = s.NumParts;
                double[] mass = new double[] {0, 0, 0, 0, 0, 0 };
                mass[1] = s[0].Mass;
                
                double redshift = Math.Pow(7.2324e-3 * 0.5 * s.Time, -2.0/3.0) - 1.0;
                double time = 1.0 / (1.0 + redshift);
                
                int flag_sfr = 0;
                int flag_feedback = 0;
                
                int[] npart_tot = new int[] {0, 0, 0, 0, 0, 0 };
                npart_tot[1] = s.NumParts;
                
                int flag_cooling = 0;
                int num_files = 1;
                
                // get size of box -- assume all three dimensins are the same
                double xmax = s[0].X;
                double xmin = xmax;
                for (int i = 1; i < s.NumParts; i++)
                {
                    if (s[i].X > xmax) xmax = s[i].X;
                    if (s[i].X < xmin) xmin = s[i].X;
                }
                double box_size = Math.Round(xmax - xmin);
                Console.Error.WriteLine("xmax = {0}\nxmin = {1}\nusing {2} as box size", xmax, xmin, box_size);
                
                double omega_0 = 1.0;
                double omega_lambda = 0.0;
                double hubble = 0.5;
                
                byte[] fill = new byte[256- 6*4- 6*8- 2*8- 2*4- 6*4- 2*4 - 4*8];
                
                int dummy = 256;
                bw.Write(dummy);
                foreach (int i in npart)
                    bw.Write(i);
                foreach (double d in mass)
                    bw.Write(d);
                
                bw.Write(time);
                bw.Write(redshift);
                bw.Write(flag_sfr);
                bw.Write(flag_feedback);
                foreach (int i in npart_tot)
                    bw.Write(i);
                bw.Write(flag_cooling);
                bw.Write(num_files);
                bw.Write(box_size);
                bw.Write(omega_0);
                bw.Write(omega_lambda);
                bw.Write(hubble);
                bw.Write(fill);
                bw.Write(dummy);
                
                // particle positions
                bw.Write(dummy);
                foreach (Particle p in s)
                {
                    bw.Write((float)p.X);
                    bw.Write((float)p.Y);
                    bw.Write((float)p.Z);
                }
                bw.Write(dummy);
                
                // particle velocities
                bw.Write(dummy);
                foreach (Particle p in s)
                {
                    // don't know if this is correct ...
                    bw.Write((float)(p.Vx / Math.Sqrt(time)));
                    bw.Write((float)(p.Vy / Math.Sqrt(time)));
                    bw.Write((float)(p.Vz / Math.Sqrt(time)));
                    //bw.Write((float)p.Vx);
                    //bw.Write((float)p.Vy);
                    //bw.Write((float)p.Vz);
                }
                bw.Write(dummy);
                
                // particle ids
                bw.Write(dummy);
                for (uint i = 0; i < s.NumParts; i++)
                {
                    bw.Write(i);
                }
                bw.Write(dummy);
            }
        }
            
              
        /*public static void Main(string[] args)
        {
            NBodySystem s = Read(args[0], Type.DncSingle);
            Write(s, "new.dat", Type.Tipsy);
        }*/
    }
}
            
